﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Linq;
using FluentResourceMapping.Util;

namespace FluentResourceMapping.AutoMapping
{
    public class AutoResourceMappingModel
    {
        private IMemberResourceMappingConvention _convention;
        private readonly IList<Type> _availableTypes;
        private IList<ResourceMap> _autoMappedTypes;
        public IAutomappingConfiguration AutomappingConfiguration { get; private set; }
        private readonly IList<IAutoMappingAlteration> _alterations = new List<IAutoMappingAlteration>();
        private readonly List<InlineOverride> inlineOverrides = new List<InlineOverride>();

        private IEnumerable<Type> ResourceTypes
        {
            get { return _availableTypes.Where(AutomappingConfiguration.IsResource); }
        }

        public AutoResourceMappingModel(IEnumerable<Type> availableMappingTypes,
                                        IAutomappingConfiguration automappingConfiguration)
        {
            AutomappingConfiguration = automappingConfiguration;
            _availableTypes = new List<Type>(availableMappingTypes);
            Convention(new DefaultMemberResourceMappingConvention());
        }

        private void BuildAutoMapping()
        {
            _autoMappedTypes = new List<ResourceMap>();

            _alterations.Each(o => o.Alter(this));

            foreach (Type type in _availableTypes.Where(o => AutomappingConfiguration.ShouldMap(o)))
            {
                Add(type, AutomappingConfiguration);
            }
        }

        private void Add(Type classType, IAutomappingConfiguration automappingConfiguration)
        {
            var autoMapType = typeof (AutoTypeResourceMapping<>).MakeGenericType(classType);
            var autoMap = Activator.CreateInstance(autoMapType, automappingConfiguration, _convention);
            //Activator.CreateInstance(autoMapType, mappedMembers);

            inlineOverrides.Where(o => o.CanOverride(classType)).Each(o => o.Apply(autoMap));

            AutoMappedTypes.Add((ResourceMap) autoMap);
        }

        public IList<ResourceMap> AutoMappedTypes
        {
            get
            {
                if (_autoMappedTypes == null)
                    BuildAutoMapping();
                return _autoMappedTypes;
            }
            private set { _autoMappedTypes = value; }
        }

        public AutoResourceMappingModel Convention(IMemberResourceMappingConvention convention)
        {
            // TODO : USar um ConventionFinder ???
            AutoMappedTypes = null;
            _convention = convention;
            _convention.ResourceTypes = ResourceTypes.ToList();

            return this;
        }

        internal IMemberResourceMappingConvention GetConvention()
        {
            return _convention;
        }

        public AutoResourceMappingModel UseOverridesFromAssemblyOf<T>()
        {
            return UseOverridesFromAssembly(typeof (T).Assembly);
        }

        public AutoResourceMappingModel UseOverridesFromAssembly(Assembly assembly)
        {
            _alterations.Add(new AutoMappingOverrideAlteration(assembly));
            return this;
        }

        // TODO: Separar em outras classes

        public bool Maps(Type type)
        {
            return AutoMappedTypes.Any(o => o.Maps(type));
        }

        public TypeResourceMapping GetMapping(Type type)
        {
            if (!Maps(type))
                throw new InvalidOperationException(string.Format("The type {0} is not mapped.", type.FullName));

            ResourceMap autoMapping = AutoMappedTypes.Single(o => o.Maps(type));

            return autoMapping.GetResourceMapping();
        }

        internal void AddOverride(Type type, Action<object> action)
        {
            inlineOverrides.Add(new InlineOverride(type, action));
        }
    }
}